package com.sny.service.impl;


import com.sny.concurrent.FileCallable;
import com.sny.constants.FileConstants;
import com.sny.dto.FileStorageDto;
import com.sny.dto.FileUploadDTO;
import com.sny.emun.FileCheckMd5Status;
import com.sny.emun.ResponseMsg;
import com.sny.exception.BizException;
import com.sny.service.IFileService;
import com.sny.service.IFileStorageService;
import com.sny.strategy.enu.UploadModeEnum;
import com.sny.util.*;
import com.sny.vo.param.FileUploadRequestParam;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

@Service
@Slf4j
public class FileServiceImpl implements IFileService {

    @Autowired
    private RedisUtil redisUtil;
    @Autowired
    private FilePathUtil filePathUtil;
    @Autowired
    private IFileStorageService fileStorageService;
    //@Value("${upload.thread.max-size}")
    private String uploadThreadMaxSize = "25";
    //@Value("${upload.queue.max-size}")
    private String uploadQueueMaxSize = "300";

    /**
     * 线程计数器
    */
    private AtomicInteger atomicInteger = new AtomicInteger(0);
  /**
   * 创建固定大小线程池执行服务
   */
    private final ExecutorService executorService = Executors.newFixedThreadPool(
          Integer.parseInt(uploadThreadMaxSize), (r) -> {
                String threadName = "uploadPool-" + atomicInteger.getAndIncrement();
                Thread thread = new Thread(r);
                thread.setName(threadName);
                return thread;
            });
  /**
   * 创建线程执行完成服务
   */
    private final CompletionService<FileUploadDTO> completionService = new ExecutorCompletionService<>(executorService,
            new LinkedBlockingDeque<>(Integer.parseInt(uploadQueueMaxSize)));




    @Override
    public FileUploadDTO sliceUpload(FileUploadRequestParam param) {
        try {
            completionService.submit(new FileCallable(UploadModeEnum.RANDOM_ACCESS, param));
            FileUploadDTO fileUploadDTO = completionService.take().get();
            return fileUploadDTO;
        } catch (InterruptedException e) {
            log.error(e.getMessage(), e);
            throw new BizException(e.getMessage(), ResponseMsg.ERROR.getCode());
        } catch (ExecutionException e) {
            log.error(e.getMessage(), e);
            throw new BizException(e.getMessage(), ResponseMsg.ERROR.getCode());
        }
    }

    @Override
    public FileUploadDTO upload(FileUploadRequestParam param) throws IOException {

        if (Objects.isNull(param.getFile())) {
            throw new BizException("file对象不能为空", ResponseMsg.ERROR.getCode());
        }
        //md5前端进行解析
//    String md5 = FileMD5Util.getFileMD5(param.getFile());
//    param.setMd5(md5);
        String md5 = param.getMd5();
        Long size = param.getFile().getSize();
        String fileExt = FileUtil.getExtension(param.getFile().getOriginalFilename());
        String fileId = UUID.randomUUID().toString();
        String filePath = filePathUtil.getPathFileId(fileId);
        File targetFile = new File(filePath);
        if (!targetFile.exists()) {
            targetFile.mkdirs();
        }
        String path = filePath + FileConstants.FILE_SEPARATORCHAR + fileId + "." + fileExt;
        FileOutputStream out = new FileOutputStream(path);
        out.write(param.getFile().getBytes());
        out.flush();
        FileUtil.close(out);
        //redis进行缓存
        redisUtil.hset(FileConstants.FILE_UPLOAD_STATUS + "_" + param.getAppId(), md5, "true");
        FileUploadDTO fileUploadDTO = FileUploadDTO
                .builder()
                .path(path)
                .fileExt(fileExt)
                .size(size)
                .fileId(fileId)
                .mtime(DateUtil.getCurrentTimeStamp())
                .uploadComplete(true)
                .build();
        //保存文件信息到数据库
        FileStorageDto fileInfo = new FileStorageDto();
        fileInfo.setMD5(param.getMd5());
        fileInfo.setFilename(param.getFile().getOriginalFilename());
        fileInfo.setSize(fileUploadDTO.getSize());
        fileInfo.setSuffix(fileUploadDTO.getFileExt());
        fileInfo.setAppId(param.getAppId());
        fileInfo.setFileId(fileId);
        fileInfo.setName(fileId);
        fileInfo.setStorageDir(FileConstants.FILE_SEPARATORCHAR + fileId);
        fileInfo.setUrl(FileConstants.FILE_SEPARATORCHAR + fileId + FileConstants.FILE_SEPARATORCHAR);
        fileStorageService.save(fileInfo);
        return fileUploadDTO;

    }

    @Override
    public FileUploadDTO checkFileMd5(FileUploadRequestParam param) throws IOException {
        Object uploadProgressObj = redisUtil.hget(FileConstants.FILE_UPLOAD_STATUS + "_" + param.getAppId(), param.getMd5());
        if (uploadProgressObj == null) {
            //查询数据库是否存在，考虑redis缓存数据丢失的情况
            FileStorageDto fileInfo = fileStorageService.findByMd5(param.getMd5());
            if (fileInfo != null) {
                String path = filePathUtil.getPathFileId(fileInfo.getFileId());
                return FileUploadDTO.builder().code(FileCheckMd5Status.FILE_UPLOADED.getValue())
                        .path(path)
                        .fileId(fileInfo.getFileId())
                        .fileExt(fileInfo.getSuffix())
                        .size(fileInfo.getSize())
                        .build();
            }else {
                //该文件没有上传过
                FileUploadDTO fileMd5DTO = FileUploadDTO.builder()
                        .code(FileCheckMd5Status.FILE_NO_UPLOAD.getValue()).build();
                return fileMd5DTO;
            }
        }
        String processingStr = uploadProgressObj.toString();
        boolean processing = Boolean.parseBoolean(processingStr);
        String value = String.valueOf(redisUtil.get(FileConstants.FILE_MD5_KEY + param.getAppId() + "_" + param.getMd5()));
        return fillFileUploadDTO(param, processing, value);
    }

    @Async("taskExecutor")
    @Override
    public void preViewTask(String fileServiceDownloadUrl,String preViewServiceUrl,String fileId, String suffix) {
        log.info("toPreview fileId:{},suffix:{}",fileId,suffix);
        String fileDowloadUrl = fileServiceDownloadUrl + fileId + "?fullfilename="+ fileId+ suffix;
        log.info("fileDowloadUrl:{}",fileDowloadUrl);
        String fileDowloadUrlEncodeUrl = EncodingUtil.encodeURIComponent(fileDowloadUrl);
        log.info("fileDowloadUrlEncodeUrl:{}",fileDowloadUrlEncodeUrl);
        String fullUrl = preViewServiceUrl + "?url="+fileDowloadUrlEncodeUrl;
        log.info(fullUrl,fullUrl);
        //异步调用
        ResponseEntity<String> res = RestTemplateUtils.get(fullUrl,String.class);
        log.info("res:{}",res);
    }

    /**
     * 填充返回文件内容信息
     *
     * @param param 文件请求信息
     * @param processing 文件上传状态信息 true 文件已经上传 flase 文件未上传成功 上传中断了
     * @param value 文件上传切边状态 配置文件路径
     * @return com.sny.dto.FileUploadDTO
     * @author wangKesheng
     * @date 2022/4/17 18:19
     */
    private FileUploadDTO fillFileUploadDTO(FileUploadRequestParam param, boolean processing,
                                            String value) throws IOException {
        if (processing) {
            //查询数据库获得文件信息
            FileStorageDto fileInfo = fileStorageService.findByMd5(param.getMd5());
            if (fileInfo != null) {
                String path = filePathUtil.getPathFileId(fileInfo.getFileId());
                return FileUploadDTO.builder().code(FileCheckMd5Status.FILE_UPLOADED.getValue())
                        .path(path)
                        .fileId(fileInfo.getFileId())
                        .fileExt(fileInfo.getSuffix())
                        .size(fileInfo.getSize())
                        .build();
            } else {
                return FileUploadDTO.builder()
                        .code(FileCheckMd5Status.FILE_NO_UPLOAD.getValue()).build();
            }
        } else {
            File confFile = new File(value);
            byte[] completeList = FileUtils.readFileToByteArray(confFile);
            List<Integer> missChunkList = new LinkedList<>();
            for (int i = 0; i < completeList.length; i++) {
                if (completeList[i] != Byte.MAX_VALUE) {
                    missChunkList.add(i);
                }
            }
            return FileUploadDTO.builder().code(FileCheckMd5Status.FILE_UPLOAD_SOME.getValue())
                    .missChunks(missChunkList).build();
        }
    }


}
